package edu.umassd.raddacl.calc;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import edu.umassd.raddacl.Cluster;
import edu.umassd.raddacl.DataSet;
import edu.umassd.raddacl.Observation;

/**
 * Class for {@link #get(Cluster) retrieving} and
 * {@link #calculateCentroid(Cluster) calculating} centroids.
 * 
 * @author Dan Avila
 * 
 */
@Component
public class ClusterMetrics
{
	private static final Logger LOG = LoggerFactory
			.getLogger("Cluster Metrics");

	@Autowired
	private DataSet dataSet;

	private Map<Cluster, Observation> centroids = new HashMap<>();
	private Map<Cluster, Observation> maxValue = new HashMap<>();
	private Map<Cluster, Observation> minValue = new HashMap<>();

	/**
	 * Gets the centroid for a particular cluster.
	 * 
	 * @param c
	 *            - the cluster.
	 * @return the centroid for that cluster.
	 */
	public Observation getCentroid(Cluster c)
	{
		return c == null ? null : get(c, centroids);
	}

	/**
	 * Gets an observation that contains the maximum value for each attribute.
	 * 
	 * @param c
	 *            - the cluster.
	 * @return maximum attribute values wrapped in an observation.
	 */
	public Observation getMax(Cluster c)
	{
		return get(c, maxValue);
	}

	/**
	 * Gets an observation that contains the lowest value for each attribute.
	 * 
	 * @param c
	 *            - the cluster.
	 * @return minimum attribute values wrapped in an observation.
	 */
	public Observation getMin(Cluster c)
	{
		return get(c, minValue);
	}

	private Observation get(Cluster c, Map<Cluster, Observation> map)
	{
		Observation value = map.get(c);

		if (value == null)
		{
			calculate(c);
			value = map.get(c);
		}

		return value;
	}

	/**
	 * Calculates a variety of metrics for a cluster.
	 * 
	 * @param c
	 *            - The cluster to calculate metrics for.
	 */
	private void calculate(Cluster c)
	{
		ObservationImpl centroid = null;
		ObservationImpl max = null;
		ObservationImpl min = null;

		try
		{
			int attributes = dataSet.getNumberOfAttributes();

			centroid = new ObservationImpl(attributes, 0.0);
			min = new ObservationImpl(attributes, Double.MAX_VALUE);
			max = new ObservationImpl(attributes, -Double.MAX_VALUE);

			for (int i = 0; i < attributes; i++)
			{
				int size = 0;

				for (Observation obs : c)
				{
					Double attr = obs.get(i);

					Double sum = centroid.get(i);
					sum += attr;

					centroid.set(i, sum);

					Double currentSmallest = min.get(i);
					Double currentLargest = max.get(i);

					currentSmallest = Math.min(currentSmallest, attr);
					currentLargest = Math.max(currentLargest, attr);

					min.set(i, currentSmallest);
					max.set(i, currentLargest);

					size++;
				}

				centroid.set(i, centroid.get(i) / size);
			}
		}
		finally
		{
			centroid = centroid == null ? new ObservationImpl() : centroid;
		}

		centroids.put(c, centroid);
		maxValue.put(c, max);
		minValue.put(c, min);
	}

	private class ObservationImpl extends ArrayList<Double> implements
			Observation
	{
		private static final long serialVersionUID = 1L;

		ObservationImpl()
		{
			super();
		}

		ObservationImpl(int size, Double value)
		{
			super(size);

			addAll(Collections.nCopies(size, value));
		}
	}
}
